package cn.edu.thu.tsquality.core.algorithm.columnrepair;
import cn.edu.thu.tsquality.core.common.algorithm.IMultiputAlgorithm;
import cn.edu.thu.tsquality.core.common.table.Header;
import cn.edu.thu.tsquality.core.common.table.Row;
import cn.edu.thu.tsquality.core.common.table.Table;
import scala.Serializable;
import weka.classifiers.Classifier;
import weka.classifiers.trees.RandomForest;
import weka.core.Instances;
import weka.core.converters.CSVLoader;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Add;
import weka.filters.unsupervised.attribute.Remove;

import java.io.*;
import java.util.*;

import static java.lang.Math.abs;

public class ColumnRepair implements IMultiputAlgorithm, Serializable{
    private String [] Attr_name;
    private int n_train,n_test;//文件的行数，未包含属性行
    private int m;//文件的列数，即属性数目
    private String [][] data_train;
    private String [][] data_test;
    private String Type;


    ColumnRepair (String Type){this.Type = Type;}


    public List<Table> run(List<Table> tables) {

        //如果使用KM算法，则type 为 -K 如果使用贪心算法 则type为 -G
        List <Table> retTables = null;

        try {
            data_preprocessing(tables);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            classifier();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (Type.equals("K")){
            try {
               retTables = KM();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (Type.equals("G"))
    {
        try {
            retTables= greed();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    File tmp = new File("train.csv"); // CSV数据文件
        tmp.delete();
    tmp = new File("test.csv");
        tmp.delete();
    tmp = new File("二分图.txt");
        tmp.delete();
        return retTables;
}


    private void data_preprocessing(List<Table> tables) throws IOException {//训练数据文件路径和测试数据文件路径两个参数

        Table train_table = tables.get(0);
        Table test_table = tables.get(1);

        m = train_table.getHeader().size();

        n_train = train_table.size();
        data_train = new String[n_train][];//存放原始训练数据


        n_test = test_table.size();
        data_test = new String[n_test][];//存放原始测试数据

        //String [][] data = new String[36300][];
        Attr_name= train_table.getHeader().toArray();//存放属性行,默认测试数据属性一致

        int Attr = 10;//写入时间序列数据向量分组数，十个数据一组
        if (Type.equals("K")){

            Attr = 5;//KM默认attr为5，贪心策略为10
        }
        System.out.println("训练文件长度："+data_train.length);
        System.out.println("测试文件长度："+data_test.length);


        for (int i=0;i<n_train;i++){
            Object [] TMP;
            TMP = train_table.get(i).toArray();
            data_train[i] = new String [m];
                for (int j = 0; j < TMP.length; j++)
                    data_train[i][j] = TMP[j].toString();
        }

        for (int i=0;i<n_test;i++){
            Object [] TMP;
            TMP = test_table.get(i).toArray();
            data_test[i] = new String [m];
            for (int j=0;j<TMP.length;j++)
                data_test[i][j] = TMP[j].toString();
        }

        try {
            File csv = new File("train.csv"); // CSV数据文件
            BufferedWriter bw = new BufferedWriter(new FileWriter(csv)); // 重写
            for (int i=1;i<=Attr+3;i++){
                bw.write("A"+String.valueOf(i)+",");
            }
            bw.write("Type");
            bw.newLine();
            for (int j=1;j<Attr_name.length;j++){
                for (int i=0;i<data_train.length;i++){
                    bw.write(data_train[i][j]+",");
                    if ((i+1)%Attr==0){
                        double sum =0,sum_min=0,diver;
                        for (int k=0;k<Attr;k++) {
                            sum += Double.parseDouble(data_train[i - k][j]);


                            if (k!=Attr-1) {
                                sum_min += abs(Double.parseDouble(data_train[i - k][j])-Double.parseDouble(data_train[i - k - 1][j]));
                            }
                        }
                        sum = sum / Attr;
                        sum_min = sum_min / (Attr-1);
                        diver =( Double.parseDouble(data_train[i][j]) - Double.parseDouble(data_train[i - Attr+1][j])) / Attr;
                        bw.write((double)Math.round(sum*100)/100+",");
                        bw.write((double)Math.round(sum_min*100)/100+",");
                        bw.write((double)Math.round(diver*100)/100+",");
                        bw.write(Attr_name[j]);
                        bw.newLine();
                    }
                }
            }
            bw.close();

            csv = new File("test.csv"); // CSV数据文件

            bw = new BufferedWriter(new FileWriter(csv)); // 重写
            for (int i=1;i<=Attr+3;i++){
                bw.write("A"+String.valueOf(i)+",");
            }
            bw.write("Type");
            bw.newLine();
            for (int j=1;j<Attr_name.length;j++){
                for (int i=0;i<data_test.length;i++){
                    bw.write(data_test[i][j]+",");
                    if ((i+1)%Attr==0){
                        double sum =0,sum_min=0,diver;
                        for (int k=0;k<Attr;k++) {
                            sum += Double.parseDouble(data_test[i - k][j]);
                            if (k!=Attr-1) {
                                sum_min += abs(Double.parseDouble(data_test[i - k][j])-Double.parseDouble(data_test[i - k - 1][j]));
                            }
                        }
                        sum = sum / Attr;
                        sum_min = sum_min / (Attr-1);
                        diver =( Double.parseDouble(data_test[i][j]) - Double.parseDouble(data_test[i - Attr+1][j])) / Attr;
                        bw.write((double)Math.round(sum*100)/100+",");
                        bw.write((double)Math.round(sum_min*100)/100+",");
                        bw.write((double)Math.round(diver*100)/100+",");
                        bw.write(Attr_name[j]);
                        bw.newLine();
                    }
                }
            }
            bw.close();

        } catch (FileNotFoundException e) {
            // File对象的创建过程中的异常捕获
            e.printStackTrace();
        } catch (IOException e) {
            // BufferedWriter在关闭对象捕捉异常
            e.printStackTrace();
        }

    }




    private void classifier() throws Exception {

        double value[][]= new double [m][m];
        int value_cnt[][] = new int [m][m];
        int N;
        Map<String , Integer> name_map = new HashMap<String , Integer> ();
        for (int i=1;i<Attr_name.length;i++){
            name_map.put(Attr_name[i],i);
        }

        CSVLoader loader = new CSVLoader();
        loader.setSource(new File("train.csv"));
        Instances data_ori = loader.getDataSet();
        Add label = new Add();
        label.setOptions(new String[]{"-T","NOM","-N","temp","-L","yes,no","-C","last"});
        label.setInputFormat(data_ori);
        Instances data = Filter.useFilter(data_ori,label);//增加二分类列的数据


        int T_cnt = data.numAttributes();
        /*for (int j=0;j<data.numInstances();j++) {
            if (data.get(j).toString(T_cnt-2).equals(Attr_name[2])) {
                data.get(j).setValue(T_cnt-1,"yes");
            } else
                data.get(j).setValue(T_cnt-1,"no");
        }*/

        N = m-1;
        //Classifier cls;
        Classifier cls [] =new Classifier[N];
        for (int i=0;i<N;i++){
            cls[i] = new RandomForest();
            for (int j=0;j<data.numInstances();j++){
                if (data.get(j).toString(T_cnt-2).equals(Attr_name[i+1])) {
                    data.get(j).setValue(T_cnt-1,"yes");//填充二分类列标签

                } else {
                    data.get(j).setValue(T_cnt - 1, "no");
                }
            }
            Remove remove1=new Remove();
            String remove_str = String.valueOf(data.numAttributes()-1);
            remove1.setOptions(new String[]{"-R",remove_str});
            remove1.setInputFormat(data);
            Instances data_tmp = Filter.useFilter(data,remove1);//删除原类标签

            data_tmp.setClassIndex(data_tmp.numAttributes()-1);
            cls[i].buildClassifier(data_tmp);


        }
        // cls = new RandomForest();
        // cls.buildClassifier(data);


        //测试集
        CSVLoader loader2 = new CSVLoader();
        loader2.setSource(new File("test.csv"));

        Instances data_test_ori = loader2.getDataSet();

        Add label_test = new Add();
        label_test.setOptions(new String[]{"-T","NOM","-N","temp","-L","yes,no","-C","last"});
        label_test.setInputFormat(data_test_ori);

        Instances data_test_temp = Filter.useFilter(data_test_ori,label_test);

        Remove remove1=new Remove();
        String remove_str = String.valueOf(data_test_temp.numAttributes()-1);
        remove1.setOptions(new String[]{"-R",remove_str});
        remove1.setInputFormat(data_test_temp);

        Instances data_test = Filter.useFilter(data_test_temp,remove1);
        data_test.setClassIndex(data_test.numAttributes() - 1);

        for (int i=0;i<data_test.numInstances();i++){

            for (int k=0;k<cls.length;k++){

                double [] t = cls[k].distributionForInstance(data_test.instance(i));
                int a=name_map.get(data_test_ori.instance(i).toString(data_test_ori.numAttributes()-1));

                value[a][k+1] += t[0];
                value_cnt[a][k+1] += 1;
            }
        }

        File fi = new File("二分图.txt"); // CSV数据文件


        BufferedWriter bw = new BufferedWriter(new FileWriter(fi)); // 重写
        for (int i=1;i<=N;i++)
        {
            for (int j=1;j<=N;j++){
                bw.write((value[i][j]/value_cnt[i][j])+" ");
            }
            bw.newLine();
        }

        bw.close();
    }


    private class KuhnMunkres { //KM二分图带权匹配算法类

        private int maxN, n, lenX, lenY;
        private double[][] weights;
        private boolean[] visitX, visitY;
        private double[] lx, ly;
        private double[] slack;
        private int[] match;

        private KuhnMunkres(int maxN) {
            this.maxN = maxN;
            this.n = maxN;
            visitX = new boolean[maxN];
            visitY = new boolean[maxN];
            lx = new double[maxN];
            ly = new double[maxN];
            slack = new double[maxN];
            match = new int[maxN];
        }

        private int[][] getMaxBipartie(double weight[][], double[] result) {
            if (!preProcess(weight)) {
                result[0] = 0.0;
                return null;
            }
            //initialize memo data for class
            //initialize label X and Y
            Arrays.fill(ly, 0);
            Arrays.fill(lx, 0);
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (lx[i] < weights[i][j])
                        lx[i] = weights[i][j];
                }
            }

            //find a match for each X point
            for (int u = 0; u < n; u++) {
                Arrays.fill(slack, 0x7fffffff);
                while (true) {
                    Arrays.fill(visitX, false);
                    Arrays.fill(visitY, false);
                    if (findPath(u))   //if find it, go on to the next point
                        break;
                    //otherwise update labels so that more edge will be added in
                    double inc = 0x7fffffff;
                    for (int v = 0; v < n; v++) {
                        if (!visitY[v] && slack[v] < inc)
                            inc = slack[v];
                    }
                    for (int i = 0; i < n; i++) {
                        if (visitX[i])
                            lx[i] -= inc;
                        if (visitY[i])
                            ly[i] += inc;
                    }
                }
            }
            result[0] = 0.0;
            for (int i = 0; i < n; i++) {
                if (match[i] >= 0)
                    result[0] += weights[match[i]][i];
            }
            return matchResult();
        }

        private int[][] matchResult() {
            int len = Math.min(lenX, lenY);
            int[][] res = new int[len][2];
            int count = 0;
            for (int i = 0; i < lenY; i++) {
                if (match[i] >= 0 && match[i] < lenX) {
                    res[count][0] = match[i];
                    res[count++][1] = i;
                }
            }
            return res;
        }

        private boolean preProcess(double[][] weight) {
            //System.out.println(weight.length);
            if (weight == null)
                return false;
            lenX = weight.length;
            lenY = weight[0].length;
            if (lenX > maxN || lenY > maxN)
                return false;
            Arrays.fill(match, -1);
            n = Math.max(lenX, lenY);
            weights = new double[n][n];
            for (int i = 0; i < n; i++)
                Arrays.fill(weights[i], 0.0);
            for (int i = 0; i < lenX; i++)
                for (int j = 0; j < lenY; j++)
                    weights[i][j] = weight[i][j];
            return true;
        }

        private boolean findPath(int u) {
            visitX[u] = true;
            for (int v = 0; v < n; v++) {
                if (!visitY[v]) {
                    double temp = lx[u] + ly[v] - weights[u][v];
                    if (temp == 0.0) {
                        visitY[v] = true;
                        if (match[v] == -1 || findPath(match[v])) {
                            match[v] = u;
                            return true;
                        }
                    } else
                        slack[v] = Math.min(slack[v], temp);
                }
            }
            return false;
        }
    }
    private Table mapping(int indexes[]){
        Header retHeader = new Header(new String[]{"待修复列名称","原列名称"});
        //System.out.println(retHeader.size());
        List<Row> returnRows = new ArrayList<>();

        for (int i=0;i<Attr_name.length;i++){
            returnRows.add(new Row(retHeader,new String[]{Attr_name[i],Attr_name[indexes[i]]}));
        }
        return new Table(retHeader,returnRows);
    }


    private Table data_repair(int indexes[]){

        Header retHeader = new Header(Attr_name);
        //System.out.println(retHeader.size());
        List<Row> returnRows = new ArrayList<>();

        for (int i=0;i<data_train.length;i++){
            returnRows.add(new Row(retHeader,data_train[i]));
        }

        for (int i=0;i<data_test.length;i++){
            String [] tmp= new String [m];
            for (int j=0;j<m;j++){
                tmp[j] = data_test[i][indexes[j]];
            }
            returnRows.add(new Row(retHeader,tmp));
        }
        return new Table(retHeader,returnRows);
    }



    private List <Table> KM() throws IOException {
        //System.out.println("KM 算法执行中：");

        List <Table> retTables = new ArrayList<>();

        double[][] graph = new double [m-1][m-1];
        BufferedReader reader = new BufferedReader(new FileReader("二分图.txt"));//换成你的文件名
        String line;
        int cnt=0;
        while(null != (line = reader.readLine())) {
            line = line.substring(0,line.length()-1);
            String item[] = line.split(" ");
            for (int i=0;i<item.length;i++){
                graph[cnt][i]=(int)(Double.parseDouble(item[i])*1000);
            }
            cnt++;
        }

        Map<String , Integer> name_map = new HashMap<String , Integer>();
        for (int i=1;i<Attr_name.length;i++){
            name_map.put(Attr_name[i],i);
        }


        KuhnMunkres T = new KuhnMunkres(name_map.size());
        double [] re = new double [1];
        int s[][] = T.getMaxBipartie(graph,re);

        int ans_cnt = 0;
        for (int i=0;i<name_map.size();i++) {
            System.out.println(Attr_name[s[i][0] + 1] + "         " + Attr_name[s[i][1] + 1]);
            if (Attr_name[s[i][0] + 1].equals(Attr_name[s[i][1] + 1]))
                ans_cnt++;
        }
        System.out.println("KM匹配的准确率为："+(double)ans_cnt/(m-1));


        int index [] = new int [m];
        index[0]= 0 ;
        for (int i=0;i<name_map.size();i++){
            index[s[i][1] + 1] = s[i][0] + 1;

        }
        retTables.add(mapping(index));
        retTables.add(data_repair(index));
        return retTables;
    }


    //接下来的两个类用于贪心算法匹配
    private class Point{
        int x;
        int y;
        double value;
    }

    public static class MyComprator implements Comparator {
        public int compare(Object arg0, Object arg1) {
            Point t1=(Point)arg0;
            Point t2=(Point)arg1;
            if (t1.value==t2.value)
                return 0;
            else
                return t1.value<t2.value? 1:-1;
        }
    }

    private List <Table> greed() throws IOException {
        List <Table> retTables = new ArrayList<>();

        double[][] graph = new double [m-1][m-1];
        BufferedReader reader = new BufferedReader(new FileReader("二分图.txt"));//换成你的文件名
        String line;
        int cnt=0;
        int N = 0;
        while(null != (line = reader.readLine())) {
            line = line.substring(0,line.length()-1);
            String item[] = line.split(" ");
            N = item.length;
            for (int i=0;i<item.length;i++){
                graph[cnt][i]=(int)(Double.parseDouble(item[i])*1000);
            }
            cnt++;
        }


        Point [] arr;
        arr  = new Point [N*N];
        for (int i=0;i<N;i++)
        {
            for (int j=0;j<N;j++)
            {
                int k = i*N+j;
                arr[k] = new Point();
                arr[k].x = i;
                arr[k].y = j;
                arr[k].value = graph[i][j];


            }
        }

        Arrays.sort(arr, new MyComprator());

        int [] tagx = new int [N];
        int [] tagy = new int [N];

        Map<String , Integer> name_map = new HashMap<String , Integer>();
        for (int i=1;i<Attr_name.length;i++){
            name_map.put(Attr_name[i],i);
        }
        int ans_cnt=0;
        for (int i=0;i<arr.length;i++){
            if (tagx[arr[i].x]==0&&tagy[arr[i].y]==0){
                tagx[arr[i].x]=arr[i].y+1;
                tagy[arr[i].y]=arr[i].x+1;
                System.out.println(Attr_name[arr[i].x+1]+"         "+Attr_name[arr[i].y+1]);
                if (Attr_name[arr[i].x+1].equals(Attr_name[arr[i].y+1]))
                    ans_cnt++;
            }

        }
        System.out.println("贪心策略匹配的准确率为："+(double)ans_cnt/(m-1));

        int index [] = new int [m];
        index[0]= 0 ;
        for (int i=0;i<m-1;i++){
            index[i+1] = tagy[i];
        }
        retTables.add(mapping(index));
        retTables.add(data_repair(index));
        return retTables;

    }
}
